package org.wattdepot.cli;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.wattdepot.cli.commands.WattDepotCliCommand;
import org.wattdepot.cli.commands.HelpCommand;
import org.wattdepot.cli.commands.QuitCommand;
import org.wattdepot.cli.commands.ListSourcesCommand;
import org.wattdepot.cli.commands.ListSummaryCommand;
import org.wattdepot.cli.commands.SensorTimeStampCommand;
import org.wattdepot.cli.commands.SensorDayCommand;
import org.wattdepot.cli.commands.PowerTimeStampCommand;
import org.wattdepot.cli.commands.PowerSamplingCommand;
import org.wattdepot.cli.commands.TotalSamplingCommand;
import org.wattdepot.cli.commands.PowerChartCommand;
import org.wattdepot.cli.commands.FuelTypesCommand;
import org.wattdepot.cli.commands.TotalPowerCommand;
import org.wattdepot.cli.commands.CarbonContentCommand;
import org.wattdepot.cli.commands.EnergyUsageCommand;
import org.wattdepot.cli.commands.EnergyGeneratedCommand;
import org.wattdepot.cli.commands.CarbonEmittedCommand;
import org.wattdepot.cli.commands.ChangeServerCommand;
import org.wattdepot.client.WattDepotClient;

/**
 * A command line interface to use as a client for a WattDepot server.
 * @author David Mau
 */
public class CommandLineInterface {
  // Variables to make PMD stop complaining
  private static String day = "day";
  private static String statistic = "statistic";
  private static String samplingInterval = "sampling-interval";
  
  private static String firstClient;
  private String currentClient = null;
  private WattDepotClient client = 
    new WattDepotClient("http://server.wattdepot.org:8182/wattdepot/");
  private static Map<String, WattDepotCliCommand> commandMap;

  // The names for the commands could change at any time.
  // This list allows someone to easily change the command names.
  // Be sure to modify the help file to avoid confusion, though.
  private static String helpName = "help";
  private static String quitName = "quit";
  private static String listSourcesName = "sources";
  private static String listSummaryName = "source";
  private static String sensorTimeStampName = "sensordata";
  private static String sensorDayName = "dailysensordata";
  private static String powerTimeStampName = "power";
  private static String powerSamplingName = "powerstats";
  private static String totalSamplingName = "total";
  private static String powerChartName = "chart";
  private static String fuelTypesName = "fueltypes";
  private static String totalPowerName = "totalpower";
  private static String carbonContentName = "carboncontent";
  private static String energyUsageName = "energyusage";
  private static String energyGeneratedName = "energygenerated";
  private static String carbonEmittedName = "energygenerated";
  private static String changeServerName = "server";
  
  // Tells the user that they entered an invalid command.
  private static String notValidCommand = "Not a valid command.\n" +
      "Type 'help' for a command list.";
  
  /**
   * Constructor for the command line interface.
   * 
   * @param providedClient A string holding a url for a WattDepot server that
   * was passed from the command line when the program was started.
   */
  public CommandLineInterface( String providedClient ) {
    boolean exitting = false;
    String input = "";
    
    // If there is a client specified, use it.  Otherwise use the default.
    if ( providedClient != null ) {
      currentClient = providedClient;
      
      // Connect to the specified Client
      client = new WattDepotClient(currentClient);
    }

    // Stop right away if we cannot contact server.
    if (!client.isHealthy()) {
      System.out.println("Failed to connect to " + client.getWattDepotUri());
      return;
    }
    // Indicate successful connection.
    System.out.println("Connected to " + client.getWattDepotUri());
    
    // Map the commands.
    resetCommands();

    System.out.println( "Welcome to WattDepot." );
    System.out.println( "Type 'help' for a list of available commands." );
    
    // Ready the buffer to receive input.
    BufferedReader inputBuffer = new BufferedReader(new InputStreamReader(System.in));
    
    try {
      // Until the user exits,
      while ( !exitting ) {
        System.out.print( ">" );
        
        // Collect input from the user.
        input = inputBuffer.readLine();
        
        // Break the input into space delimited pieces.
        String[] splitInput = input.split(" ");
        
        // Put the input into a list for easy use
        ArrayList<String> inputList = new ArrayList<String>();
        for ( String singleInput : splitInput ) {
          inputList.add( singleInput );
        }

        // Get the command from the map.
        WattDepotCliCommand calledCommand = commandMap.get( splitInput[0] );
        
        // If what the user input does not match any commands in the list,
        if ( calledCommand == null ) {
          // Tell the user that the command isn't a valid command.
          System.out.println(notValidCommand);
        }
        // Otherwise, the user called a command on the list.
        else {
          
          // If the help command was called,
          if ( inputList.get(0).compareToIgnoreCase( helpName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 1) ) {
              List<String> args = new ArrayList<String>();
              
              // Get the help output from the command.
              System.out.println( calledCommand.doCommand( args ) );
            }
          }
          // If the quit command was called,
          else if ( inputList.get(0).compareToIgnoreCase( quitName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 1) ) {
              List<String> args = new ArrayList<String>();
              
              // Get the quit output from the command.
              String quitOutput = calledCommand.doCommand( args );
              // Print out the output.
              System.out.println( quitOutput );
              
              // If the output returned okay, exit the program.
              if ( quitOutput.compareTo( WattDepotCliCommand.closingProgram ) == 0 ) {
                // Exit the program.
                exitting = true; 
              }
            }
          }
          // If the change server command was called,
          else if ( inputList.get(0).compareToIgnoreCase( changeServerName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 2) ) {
              List<String> args = Arrays.asList( inputList.get(1) );
              
              // Get the server change output from the command.
              String changeOutput = calledCommand.doCommand( args );
              // Print out the output.
              System.out.println( changeOutput );
              
              // If the output returned okay, change the server.
              if ( changeOutput.contains( WattDepotCliCommand.serverChange ) ) {
                // Change the server.
                String tempClientName = inputList.get(1);
                
                // Connect to the specified Client
                WattDepotClient trialClient = new WattDepotClient( tempClientName );
                
                // If the server is fine, save the changes.
                if ( trialClient.isHealthy()) {
                  // Save the server change.
                  currentClient = tempClientName;
                  client = trialClient;
                  
                  // Indicate successful connection.
                  System.out.println("Connected to " + client.getWattDepotUri());
                  
                  // Map the commands to the new client.
                  resetCommands();
                  
                }
                // Otherwise, cancel the changes.
                else {
                  System.out.println("Failed to connect to " + trialClient.getWattDepotUri());
                }
              }
            }
            else {
              // Tell the user that the command isn't a valid command.
              System.out.println(notValidCommand);
            }
          }
          // If the list sources command was called,
          else if ( inputList.get(0).compareToIgnoreCase( listSourcesName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 1) ) {
              List<String> args = new ArrayList<String>();
              
              // Get the list sources output from the command.
              System.out.println( calledCommand.doCommand( args ) );
            }
          }
          // If the list summary command was called,
          else if ( inputList.get(0).compareToIgnoreCase( listSummaryName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 3) ) {
              // Check the arguments to see if they are correct.
              if ( inputList.get(2).compareToIgnoreCase( "summary" ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1) );
                
                // Get the list summary output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the sensor time stamp command was called,
          else if ( inputList.get(0).compareToIgnoreCase( sensorTimeStampName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 4) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( "timestamp" ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3) );
                
                // Get the sensor time stamp output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the sensor day command was called,
          else if ( inputList.get(0).compareToIgnoreCase( sensorDayName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 4) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( day ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3) );
                
                // Get the sensor day output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the power time stamp command was called,
          else if ( inputList.get(0).compareToIgnoreCase( powerTimeStampName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 4) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( "timestamp" ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3) );
                
                // Get the power time stamp output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the power sampling command was called,
          else if ( inputList.get(0).compareToIgnoreCase( powerSamplingName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 8) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( day ) == 0 &&
                   inputList.get(4).compareToIgnoreCase( samplingInterval ) == 0 && 
                   inputList.get(6).compareToIgnoreCase( statistic ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3),
                    inputList.get(5), inputList.get(7) );
                
                // Get the power sampling output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the total sampling command was called,
          else if ( inputList.get(0).compareToIgnoreCase( totalSamplingName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 7) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(3).compareToIgnoreCase( day ) == 0 &&
                   inputList.get(5).compareToIgnoreCase( samplingInterval ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(2),
                    inputList.get(4), inputList.get(6) );
                
                // Get the total sampling output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the power chart command was called,
          else if ( inputList.get(0).compareToIgnoreCase( powerChartName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 10) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(1).compareToIgnoreCase( "power" ) == 0 &&
                  inputList.get(6).compareToIgnoreCase( samplingInterval ) == 0 && 
                  inputList.get(8).compareToIgnoreCase( "file" ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(2), inputList.get(3),
                    inputList.get(4), inputList.get(5), inputList.get(7), inputList.get(9) );
                
                // Get the power chart output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the fuel types command was called,
          else if ( inputList.get(0).compareToIgnoreCase( fuelTypesName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 2) ) {
              // Create a new List to store the arguments for the command.
              List<String> args = Arrays.asList( inputList.get(1) );
              
              // Get the fuel types output from the command.
              System.out.println( calledCommand.doCommand( args ) );
            }
          }
          // If the total power command was called,
          else if ( inputList.get(0).compareToIgnoreCase( totalPowerName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 5) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(3).compareToIgnoreCase( "fuelType" ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(2),
                    inputList.get(4) );
                
                // Get the total power output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the carbon content command was called,
          else if ( inputList.get(0).compareToIgnoreCase( carbonContentName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck(inputList, 5) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(3).compareToIgnoreCase( samplingInterval ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(2),
                    inputList.get(4) );
                
                // Get the carbon content output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the energy usage command was called,
          else if ( inputList.get(0).compareToIgnoreCase( energyUsageName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck( inputList, 7) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( day ) == 0 &&
                   inputList.get(5).compareToIgnoreCase( statistic ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3),
                    inputList.get(4), inputList.get(6) );
                
                // Get the energy usage output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the energy generated command was called,
          else if ( inputList.get(0).compareToIgnoreCase( energyGeneratedName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck( inputList, 7) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( day ) == 0 &&
                   inputList.get(5).compareToIgnoreCase( statistic ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3),
                    inputList.get(4), inputList.get(6) );
                
                // Get the energy generated output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          // If the carbon emitted command was called,
          else if ( inputList.get(0).compareToIgnoreCase( carbonEmittedName ) == 0 ) {
            // Check argument size.
            if ( argumentCheck( inputList, 7) ) {
              // Check to see if the arguments are correct.
              if ( inputList.get(2).compareToIgnoreCase( day ) == 0 &&
                   inputList.get(5).compareToIgnoreCase( statistic ) == 0 ) {
                // Create a new List to store the arguments for the command.
                List<String> args = Arrays.asList( inputList.get(1), inputList.get(3),
                    inputList.get(4), inputList.get(6) );
                
                // Get the carbon emitted output from the command.
                System.out.println( calledCommand.doCommand( args ) );
              }
              else {
                // Tell the user that the command isn't a valid command.
                System.out.println(notValidCommand);
              }
            }
          }
          else {
            // Tell the user that the command isn't a valid command.
            System.out.println(notValidCommand);
          }
          
        }
      }
    }
    catch ( IOException e ) {
      System.out.println( "An I/O error occurred." );
      System.out.println( "Exitting client. ");
    }
  }
  
  /**
   * This method will check the size of the given list.  If the size matches the
   * number given as a parameter, it will return true.
   * Otherwise, this method will return false and print an appropriate error message.
   * 
   * @param argList A list of arguments.
   * @param exactNumber How many arguments should be inside the list.
   * @return True if the number of arguments matches the number of arguments in the list.
   */
  private boolean argumentCheck( List<String> argList, int exactNumber ) {
    boolean result = false;
    if ( argList.size() > exactNumber ) {
      System.out.println( WattDepotCliCommand.tooManyArguments );
    }
    else if ( argList.size() < exactNumber ) {
      System.out.println( WattDepotCliCommand.missingArguments );
    }
    else {
      result = true;
    }
    
    return result;
  }
  
  /**
   * This method resets the commands if the user should so choose to change clients in
   * mid-session.
   */
  private void resetCommands() {
    commandMap = new HashMap<String, WattDepotCliCommand>();
    commandMap.put(helpName, new HelpCommand(currentClient));
    commandMap.put(quitName, new QuitCommand(currentClient));
    commandMap.put(listSourcesName, new ListSourcesCommand(currentClient));
    commandMap.put(listSummaryName, new ListSummaryCommand(currentClient));
    commandMap.put(sensorTimeStampName, new SensorTimeStampCommand(currentClient));
    commandMap.put(sensorDayName, new SensorDayCommand(currentClient));
    commandMap.put(powerTimeStampName, new PowerTimeStampCommand(currentClient));
    commandMap.put(powerSamplingName, new PowerSamplingCommand(currentClient));
    commandMap.put(totalSamplingName, new TotalSamplingCommand(currentClient));
    commandMap.put(powerChartName, new PowerChartCommand(currentClient));
    commandMap.put(fuelTypesName, new FuelTypesCommand(currentClient));
    commandMap.put(totalPowerName, new TotalPowerCommand(currentClient));
    commandMap.put(carbonContentName, new CarbonContentCommand(currentClient));
    commandMap.put(energyUsageName, new EnergyUsageCommand(currentClient));
    commandMap.put(energyGeneratedName, new EnergyGeneratedCommand(currentClient));
    commandMap.put(carbonEmittedName, new CarbonEmittedCommand(currentClient));
    commandMap.put(changeServerName, new ChangeServerCommand(currentClient));
  }
  
  /**
   * This main method handles the command line arguments, if any for the client.
   * @param args Optional argument for specifying a WattDepot server.
   */
  public static void main(String[] args) {
    // Place the command line arguments into an ArrayList
    ArrayList<String> argumentAnalysis = new ArrayList<String>();
    for ( String singleInput : args ) {
      argumentAnalysis.add( singleInput );
    }
    
    // If the list's size is greater than one,
    if ( argumentAnalysis.size() > 1 ) {
      // Output an error.
      System.out.println( "Error: Too many arguments passed to command." );
      System.out.println( "Usage: java -jar wattdepotcli.jar (WattDepotURL)");
      return;
    }

    if ( argumentAnalysis.size() == 1 ) {
      firstClient = argumentAnalysis.get(0);
    }
    
    // All preliminary tests pass.
    // Start the command line interface.
    new CommandLineInterface( firstClient );
  }

}
